package com.googlecode.lohr.tools.language;

import com.googlecode.lingwah.Grammar;
import com.googlecode.lingwah.Parser;
import com.googlecode.lingwah.parser.ChoiceParser;
import com.googlecode.lingwah.parser.ParserReference;
import com.googlecode.lingwah.parser.SpacedSequenceParser;

/**
 * A grammar for the Lohr language.
 * 
 * @author Ted Stockwell
 */
public class LohrGrammar extends Grammar
{
	public final ParserReference Statement= ref();
	public final ParserReference Expression= ref();
	public final ParserReference TypeReference= ref();
	
	//
	// Keywords
	//
	public final Parser DEF= str("def");
	public final Parser PACKAGE= str("package");
	public final Parser IMPORT= str("import");
	public final Parser PUBLIC= str("public");
	public final Parser PRIVATE= str("private");
	public final Parser PROTECTED= str("protected");
	public final Parser VAR= str("var");
	public final Parser THIS= str("this");
	public final Parser RETURN= str("return");
	

	//
	// Operators
	//
	public final ChoiceParser PrefixOperator= choice();
	public final ChoiceParser PostfixOperator= choice();
	public final ChoiceParser InfixOperator= choice();
	public final Parser ASSIGN= str("=").optionFor(InfixOperator);
	/**
	 * NOTE: lohr uses '|<" and ">|" instead of "<" and ">" for lessThan and 
	 * greaterThan operators in order to avoid syntactic confusion with the 
	 * "<" and ">" punctuation used to denote parameterized types.
	 * Since it is assumed that lohr code will most often contain far more 
	 * parameterized types than |< and >| operators it was decided to change the 
	 * operator syntax. 
	 */
	public final Parser GTOP= str("|<").optionFor(InfixOperator);
	public final Parser LTOP= str(">|").optionFor(InfixOperator);
	public final Parser TILDE= str("~").optionFor(PrefixOperator);
	public final Parser HOOK= str("?").optionFor(InfixOperator);
	public final Parser EQ= str("==").optionFor(InfixOperator);
	public final Parser LE= str("<=").optionFor(InfixOperator);
	public final Parser GE= str(">=").optionFor(InfixOperator);
	public final Parser NE= str("!=").optionFor(InfixOperator);
	public final Parser SC_OR= str("||").optionFor(InfixOperator);
	public final Parser SC_AND= str("&&").optionFor(InfixOperator);
	public final Parser INCR= str("++").optionFor(PrefixOperator).optionFor(PostfixOperator);
	public final Parser DECR= str("--").optionFor(PrefixOperator).optionFor(PostfixOperator);
	public final Parser PLUS= str("+").optionFor(InfixOperator);
	public final Parser MINUS= str("-").optionFor(InfixOperator).optionFor(PrefixOperator);
	public final Parser STAR= str("*").optionFor(InfixOperator);
	public final Parser SLASH= str("/").optionFor(InfixOperator);
	public final Parser LOGICAL_AND= str("&").optionFor(InfixOperator);
	public final Parser LOGICAL_OR= str("|").optionFor(InfixOperator);
	public final Parser XOR= str("^").optionFor(InfixOperator);
	public final Parser REM= str("%").optionFor(InfixOperator);
	public final Parser LSHIFT= str("<<").optionFor(InfixOperator);
	public final Parser RSHIFT= str(">>").optionFor(InfixOperator);
	public final Parser URSHIFT= str(">>>").optionFor(InfixOperator);
	public final Parser PLUSASSIGN= str("+=").optionFor(InfixOperator);
	public final Parser MINUSASSIGN= str("-=").optionFor(InfixOperator);
	public final Parser STARASSIGN= str("*=").optionFor(InfixOperator);
	public final Parser SLASHASSIGN= str("/=").optionFor(InfixOperator);
	public final Parser ANDASSIGN= str("&=").optionFor(InfixOperator);
	public final Parser ORASSIGN= str("|=").optionFor(InfixOperator);
	public final Parser XORASSIGN= str("^=").optionFor(InfixOperator);
	public final Parser REMASSIGN= str("%=").optionFor(InfixOperator);
	public final Parser LSHIFTASSIGN= str("<<=").optionFor(InfixOperator);
	public final Parser RSHIFTASSIGN= str(">>=").optionFor(InfixOperator);
	public final Parser URSHIFTASSIGN= str(">>>=").optionFor(InfixOperator);
	public final Parser NOT= str("!").optionFor(PrefixOperator);

	//
	// Punctuation
	//
	public final Parser GT= str(">");
	public final Parser LT= str("<");
	public final Parser LPAREN= str("("); 
	public final Parser RPAREN= str(")");  
	public final Parser LBRACE= str("{"); 
	public final Parser RBRACE= str("}");  
	public final Parser LBRACKET= str("[");  
	public final Parser RBRACKET= str("]"); 
	public final Parser SEMICOLON= str(";"); 
	public final Parser COMMA= str(","); 
	public final Parser DOT= str("."); 
	public final Parser COLON= str(":"); 
	public final Parser DOUBLE_DOT= str(".."); 
	public final Parser DOUBLE_COLON= str("::"); 
	public final Parser THICK_ARROW= str("=>"); 
	public final Parser ARROW= str("->");
	public final Parser DOUBLE_QUOTE= str("\"");
	public final Parser SINGLE_QUOTE= str("\'");
	public final Parser BACKSLASH= str("\\");
	public final Parser PIPE= str("|");
	

	public final Parser LineTerminator= choice(str("\n"), str("\r"), str("\n\r"), str("\r\n")); 
	public final Parser InputCharacter= excluding(anyChar(), LineTerminator);
	public final Parser EscapedCharacter= choice(SINGLE_QUOTE, DOUBLE_QUOTE, str("b"), str("f"), str("t"), str("n"), str("\\"));
	public final Parser EscapeSequence= seq(BACKSLASH, EscapedCharacter);
	public final Parser NonzeroDigit= range('1', '9');
	public final Parser Digit= choice(str('0'), NonzeroDigit);
	public final Parser IdentifierLetter= choice(range('a', 'z'), range('A', 'Z'), str('_'), str('@'), str('$'), str('#'));
	public final Parser IdentifierLetterOrDigit= choice(IdentifierLetter, Digit);
	public final Parser IdentifierName= seq(IdentifierLetter, opt(longest(IdentifierLetterOrDigit)));
	
	
	// lexical comments
	public final ChoiceParser CommentText= choice(); 
	
   	public final Parser NotAt = excluding(InputCharacter, str("@"));
   	public final Parser EndOfLineComment= 
   		seq(str("//"), opt(seq(NotAt, zeroOrMore(InputCharacter))), LineTerminator);
   	public final Parser NotStar=
		choice(LineTerminator, excluding(InputCharacter, str("*")));
	public final Parser NotStarNotAt=
		choice(LineTerminator, excluding(InputCharacter, str("*"), str("@")));
   	public final Parser InLineComment= 
   		seq(str("/*"), opt(seq(NotStarNotAt, CommentText)), str("*/"));
	public final Parser NotStarNotSlash= 
		choice(LineTerminator, excluding(InputCharacter, str("*"), str("/")));
	public final Parser StarCommentText= 
		seq(str("*"), opt(seq(NotStarNotSlash, CommentText)));
	public final ChoiceParser LexicalComment= 
		choice(EndOfLineComment, InLineComment); 

	public final Parser DocumentationComment= seq(str("/**"), CommentText, str("*/"));
	
	{
		CommentText.addChoice(seq(zeroOrMore(NotStar), opt(StarCommentText)));
	}
	
	// whitespace
	public final Parser ws= 
		rep(choice(str(" "), str("\t"), str("\f"), LineTerminator, InLineComment));
	public final Parser optws= opt(ws); 
	
	/**
	 * Creates a sequence matcher that expects white space between all the 
	 * elements 
	 */
	public final Parser sseq(Parser...matchers) {
		return new SpacedSequenceParser(optws, matchers);
	}
	
	public final Parser Comma= 
			seq(optws, COMMA, optws);
	public final Parser SemiColon= 
			seq(optws, SEMICOLON, optws);
		
	public final Parser Dot= 
			seq(optws, DOT, optws);

	//
	// Literals
	//
	public final Parser OctalPrefix= str("0");
	public final Parser OctalDigit= range('0', '7');
	public final Parser OctalLiteral= seq(OctalPrefix, opt(str("_")), OctalDigit, zeroOrMore(seq(opt(str("_")), OctalDigit))); 

	public final Parser HexPrefix= choice(str("0x"), str("0X"));
	public final Parser HexDigit= choice(Digit, range('a', 'f'), range('A', 'F'));
	public final Parser HexLiteral= seq(HexPrefix, HexDigit, zeroOrMore(seq(opt(str("_")), HexDigit))); 

	public final Parser BinaryPrefix= choice(str("0b"), str("0B"));
	public final Parser BinaryDigit= choice(str("0"), str("1"));
	public final Parser BinaryLiteral= seq(BinaryPrefix, BinaryDigit, zeroOrMore(seq(opt(str("_")), BinaryDigit))); 

	public final Parser DecimalLiteral= choice(seq(NonzeroDigit, zeroOrMore( seq(opt(str("_")), Digit ))), string("0"));

	public final Parser NaturalLiteral= choice(BinaryLiteral, HexLiteral, OctalLiteral, DecimalLiteral );

	public final Parser StringCharacter= choice(EscapeSequence, excluding(InputCharacter, DOUBLE_QUOTE, str("\\")));
	public final Parser StringLiteral= seq(DOUBLE_QUOTE, zeroOrMore(StringCharacter), DOUBLE_QUOTE);
//	public final Parser ArrayInitializer= sseq(LBRACE, zeroOrMore(Expression).separatedBy(Comma), RBRACE);
//	public final Parser ArrayLiteral= sseq(TypeReference, oneOrMore(sseq(LBRACKET, RBRACKET)), ArrayInitializer);

	public final Parser BooleanLiteral=  choice(string("true"), string("false"));
	
	public final Parser NullLiteral= string("null");
	public final Parser ListLiteral= sseq(LBRACKET, zeroOrMore(Expression), RBRACKET);
	public final Parser MapElement= sseq(Expression, COLON, Expression);
	public final Parser MapLiteral= sseq(LBRACKET, zeroOrMore(MapElement), RBRACKET);
	
	public final Parser NamedTypeReference= sseq(opt(sseq(IdentifierName, COLON)), TypeReference);
	public final Parser TypeParameterDeclaration= sseq(LT, oneOrMore(NamedTypeReference).separatedBy(Comma), GT);
	public final Parser ParameterizedIdentifier= sseq(choice(IdentifierName), opt(TypeParameterDeclaration));
	
	public final Parser ClosureParameterDeclaration= sseq(PIPE, oneOrMore(NamedTypeReference).separatedBy(Comma), PIPE);
	public final Parser ClosureReturnTypeDeclaration= sseq(LT, NamedTypeReference, GT);
	public final Parser ClosureBody= sseq(Statement, zeroOrMore(sseq(opt(SemiColon), Statement)), opt(SemiColon));
	public final Parser ClosureLiteral= sseq(opt(ClosureParameterDeclaration), LBRACE, opt(ClosureBody), RBRACE);
	
	public final Parser PackageName= oneOrMore(IdentifierName).separatedBy(Dot);
	public final Parser PackageDeclaration= sseq(str("package"), PackageName, SemiColon);
	public final Parser ImportProtocol= sseq(str("from"), str(":"), IdentifierName);
	public final Parser ImportDeclaration= choice(
			sseq(str("import"), opt(ImportProtocol), PackageName, SemiColon),
			sseq(str("import"), PackageName, opt(ImportProtocol), SemiColon),
			sseq(str("import"), LPAREN, opt(ImportProtocol), PackageName, RPAREN, SemiColon),
			sseq(str("import"), LPAREN, PackageName, opt(sseq(COMMA, ImportProtocol)), RPAREN, SemiColon),
			sseq(str("import"), LPAREN, opt(sseq(ImportProtocol, COMMA)), PackageName, RPAREN, SemiColon),
			sseq(str("import"), LPAREN, PackageName, RPAREN, opt(ImportProtocol), SemiColon));
	public final Parser Imports= zeroOrMore(ImportDeclaration);
	
	

	public final Parser Literal= choice(BooleanLiteral, NaturalLiteral, StringLiteral, NullLiteral, ListLiteral, MapLiteral, ClosureLiteral);
	
	public final Parser OperatorExpression= choice(
			sseq(PrefixOperator, Expression), 
			sseq(Expression, PostfixOperator), 
			sseq(Expression, InfixOperator, Expression));
	
	public final Parser AccessModifier= choice(PRIVATE, PROTECTED, PUBLIC, DEF, VAR);
	
	public final ParserReference ArgumentListDeclaration= ref();
	public final ParserReference ReferenceExpression= ref();
	public final ParserReference MethodInvocation= ref();
	public final Parser SlotDefinition= sseq(opt(AccessModifier), IdentifierName, opt(sseq(COLON, TypeReference)), ASSIGN, Expression);
	public final Parser NamedExpression= sseq(IdentifierName, COLON, Expression);
	public final Parser ArgumentExpression= choice(Expression, NamedExpression);
	{
		TypeReference.define(oneOrMore(ParameterizedIdentifier).separatedBy(Dot));
		
		ReferenceExpression.define(sseq(
			opt(sseq(choice(ReferenceExpression, MethodInvocation), DOT)), ParameterizedIdentifier));
		
		MethodInvocation.define(sseq(
			opt(sseq(ReferenceExpression, DOT)), 
			ParameterizedIdentifier, 
			choice(
				sseq(LPAREN, zeroOrMore(ArgumentExpression).separatedBy(Comma), RPAREN),
				ws),
			zeroOrMore(choice(NamedExpression, ClosureLiteral)).separatedBy(choice(Comma, ws))));
		
		Statement.define(choice(SlotDefinition, MethodInvocation));
		
		Expression.define(choice(MethodInvocation, ReferenceExpression, OperatorExpression, Literal));
	}
	


	public final Parser CompilationUnit= seq(optws, sseq(PackageDeclaration, Imports, ClosureBody), optws);

	public static final LohrGrammar DEFINITION= new LohrGrammar();
	private LohrGrammar() {
		init();
	}
}
